package Bio::Tools::Mlst::PubMLST;

use base qw(Bio::Tools::Mlst);
use 5.008008;
use strict;
use warnings;
use Data::Dumper;

# Items to export into callers namespace by default. Note: do not export
# names by default without a very good reason. Use EXPORT_OK instead.
# Do not simply export all your public functions/methods/constants.

# This allows declaration	use MLST ':all';
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
# will save memory.
our %EXPORT_TAGS = ( 'all' => [ qw(
	
) ] );

our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );

our @EXPORT = qw(
	
);

our $VERSION = '0.03';

# TODO allow for the proxy and uri to be changed in case there is a local database
# TODO make some kind of validation for arguments in all subroutines
sub new{
  use SOAP::Lite;
  my($class,$args)=@_;
  my($self);
  
  # default variables for this subroutine, which will eventually affect the rest of the class
  my %default=(database=>'neisseria');
  
  $self={
    SOAP=>$class->SUPER::_pubmlstSoap,
    defaultVar=>\%default
  };
  bless($self, $class);
  
  # default arguments for the rest of the class
  if(!defined($$args{database})){
    # set default database and therefore a default repository down the line
    $$args{database}=$self->{defaultVar}->{database};
  }
  $self->_setDefaultArguments($args);
  $args=$self->_processArgs($args);
    
  return $self;
}
# blast($database,$sequence,$numresults)
# returns hits in SearchIO object
# TODO accept a Seq object
sub blast{
  use Bio::SearchIO;
  use IO::String;
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  # @hit is an array of hits with each hit's keys
  # ={length=>433, alignment=>433, mismatches, id, locus, gaps}
  my @hit=$self->_pubmlstBlast($args);
  $$args{hit}=\@hit; # add hits onto the arguments
  my $in=$self->_pubmlstProcessBlastResults($args);
  return $in;
}
sub getAlleleCount{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my $count=$self->_pubmlstGetAlleleCount($args);
  return $count;
}
# akin to getAlleleSequence
# but it returns a Seq object instead to make it OO
sub getAllele{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my $seq=$self->_pubmlstGetAllele($args);
  return $seq;
}
# akin to get getAlleleSequences
# but returns seq objects
sub getAlleles{
  use Bio::Seq;
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my @seq=$self->_pubmlstGetAlleles($args);
  return @seq;
}

#Returns URL of a tab-delimited list of allelic profile definitions.
sub getAlleleSequencesURL{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my $url=$self->_pubmlstGetAlleleSequencesURL($args);
  return $url;
}
# Returns clonal complex defined for a ST
sub getClonalComplex{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my $CC=$self->_pubmlstGetClonalComplex($args);
  return $CC;
}
# returns a list of all clonal complexes in a database
sub getClonalComplexes{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my @CC=$self->_pubmlstGetClonalComplexes($args);
  return @CC;
}
# returns list of databases in the form of an array of hashes
sub getDatabaseList{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my @databases=$self->_getPubmlstDatabaseList($args);
  return @databases;
}
sub getForwardConsensus{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my $seq=$self->_pubmlstGetForwardConsensus($args);
  return $seq;
}
sub getReverseConsensus{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my $seq=$self->_pubmlstGetReverseConsensus($args);
  return $seq;
}
sub getIsolate{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my(%isolate);
  my $soapResponse = $self->soap->getIsolate($$args{database},$$args{id});
  unless ($soapResponse->fault){
    for my $soapData ($soapResponse->dataof('//field')) {
      my $key=$soapData->value->{'name'};
      my $value=$soapData->value->{'value'};
      $isolate{$key}=$value;
    }
  } else {
      $self->_warn($soapResponse);
  }
  return %isolate;
}
sub getIsolateCount{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my $count=$self->_pubmlstGetIsolateCount($args);
  return $count;
}
# Returns isolate fields defined for database.
# Each field is a hash of name, type, and description
sub getIsolateFields{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my (@isolateField);
  my $soapResponse = $self->soap->getIsolateFields($$args{database});
  unless ($soapResponse->fault){
      for my $soapData ($soapResponse->dataof('//fieldDef')) {
        push(@isolateField,$soapData->value);
      }
  } else {
      $self->_error($soapResponse);
  }
  return @isolateField;
}
# Returns list of loci defined for database.
sub getLocusList{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my(@loci);
  my $soapResponse = $self->{SOAP}->getLocusList($$args{database});
  unless ($soapResponse->fault){
      for my $locus ($soapResponse->valueof('//locus')) {
        push @loci, $locus
      }
  } else {
      $self->_error($soapResponse);
  }
  return @loci;
}
# Returns standard length of locus.
# If locus is variable, then return 0
sub getLocusLength{
  use Bio::Location::Simple;
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my($min_end,$max_end,$location);
  my($length,$variable);
  my $soapResponse = $self->{SOAP}->getLocusLength($$args{database},$$args{locus});
  unless ($soapResponse->fault){
      $length=($soapResponse->valueof('//length'));
      $min_end=$max_end=$length;
      if($soapResponse->valueof('//variable')){
        $length=0;
      }
  } else {
      $self->_error($soapResponse);
  }
  return $length;
}
#Returns allelic profile defined for a ST.
#Including CC and ST
sub getProfile{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my(%profile);
  my $soapResponse = $self->{SOAP}->getProfile($$args{database},$$args{ST});
  unless ($soapResponse->fault){
    for my $t ($soapResponse->valueof('//alleleNumber')) {
      $profile{$t->{'locus'}}=$t->{'id'};
    }
    $profile{'CC'}=$soapResponse->valueof('//complex') || '';
    $profile{'ST'}=$$args{'ST'};
  } else {
      $self->_error($soapResponse);
  }
  return %profile;
}
# Returns number of defined profiles.
sub getProfileCount{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my($num);
  my $soapResponse = $self->{SOAP}->getProfileCount($$args{database});
  unless ($soapResponse->fault){
      $num=$soapResponse->result();
  } else {
      $self->_error($soapResponse);
  }
  return $num;
}
# Returns URL of a page of 
# tab-delimited list of allelic profile definitions.
sub getProfileListURL{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my($url);
  my $soapResponse = $self->{SOAP}->getProfileListURL($$args{database});
  unless ($soapResponse->fault){
      $url=$soapResponse->result();
  } else {
      $self->_error($soapResponse);
  }
  return $url;
}
# Returns list of STs that match at at least the specified number of loci. 
# Query is by allelic profile.
# TODO if the profile hash exists (e.g. {abcZ=>1,adk_=>10...}), 
#         ignore the profile array and just use the hash
#####Sample arguments#########
#my $database = 'neisseria';
#my $match = 6;
#my @profile = (2,3,4,3,8,4,6);
##############################
sub getRelatedIsolateIdsByProfile{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  #my @profile=@{$$args{profile}};
  my %profile=%{$$args{profile}};
  #my @loci=$self->getLocusList($args);
  my (@matchingIds);
  
  # generate a profileElements array for passing into the SOAP request
  my @profileElements;
  while( my($locus,$allele)=each(%profile) ){
      push @profileElements,SOAP::Data->name('alleleNumber' => \SOAP::Data->value(
          SOAP::Data->name('locus' => $locus),
          SOAP::Data->name('id' => $allele)
        )
      );
  }
  my $soapResponse = $self->soap->getRelatedIsolateIdsByProfile($$args{database},$$args{match},@profileElements);
  unless ($soapResponse->fault){
    for my $t ($soapResponse->valueof('//id')) {
      push(@matchingIds,$t);
    }
  } else {
    $self->_error($soapResponse);
  }
  return @matchingIds;
}
# Returns list of isolate ids that have profiles that match specified ST at at least the specified number of loci.
sub getRelatedIsolateIdsByST{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my(@id);
  my $soapResponse = $self->soap->getRelatedIsolateIdsByST($$args{database},$$args{match},$$args{ST});
  unless ($soapResponse->fault){
    for my $t ($soapResponse->valueof('//id')) {
	    push(@id,$t);
    }
  } else {
    $self->_error($soapResponse);
  }
  return @id;
}
# Returns profiles that match at at least the specified number of loci. Query is by profile.
sub getRelatedProfilesByProfile{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my(@profile); # profiles to return
  my($soapResponse);
  
  my @profileElements;
  while( my($locus,$allele)=each(%{$$args{profile}}) ){
    push @profileElements,SOAP::Data->name('alleleNumber' => \SOAP::Data->value(
      SOAP::Data->name('locus' => $locus),
      SOAP::Data->name('id' => $allele)));
  }
  $soapResponse = $self->soap->getRelatedProfilesByProfile($$args{database},$$args{match},@profileElements);
  unless ($soapResponse->fault){
    my $i=0;
    my @loci=$self->getLocusList($args);
    my @alleleNumbers = $soapResponse->dataof('//STDef/profile/alleleNumber');
    for my $soapData ($soapResponse->dataof('//STDef')) {
      $profile[$i]{'ST'}=$soapData->value->{'ST'};
      $profile[$i]{'CC'}=$soapData->value->{'complex'};
      #while( my($locus,$allele)=each(%{$$args{profile}}) ){
      for (my $j=0; $j<scalar @loci; $j++){
        $profile[$i]{$alleleNumbers[$j]->value->{'locus'}}=$alleleNumbers[$j]->value->{'id'};
      }
      $i++;
    }
  } else {
    $self->_error($soapResponse);
  }
  return @profile;
}
# Returns profiles that match at at least the specified number of loci. Query is by ST.
sub getRelatedProfilesByST{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my(@profile); # profiles to return
  my @loci=$self->getLocusList($args);
  
  my $soapResponse = $self->soap->getRelatedProfilesByST($$args{database},$$args{match},$$args{ST});
  unless ($soapResponse->fault){
    my $i=0;
    my @alleleNumbers = $soapResponse->dataof('//STDef/profile/alleleNumber');
    for my $soapData ($soapResponse->dataof('//STDef')) {
      $profile[$i]{'ST'}=$soapData->value->{'ST'};
      $profile[$i]{'CC'}=$soapData->value->{'complex'};
      for (my $j=0; $j<scalar @loci; $j++){
        $profile[$i]{$alleleNumbers[$j]->value->{'locus'}}=$alleleNumbers[$j]->value->{'id'};
      }
      $i++;
    }
  } else {
    $self->_error($soapResponse);
  }
  return @profile;
}
sub getRelatedSTsByProfile{
  use SOAP::Lite;
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my(@ST); # STs to return
  my @profileElements;
  while( my($locus,$allele)=each(%{$$args{profile}}) ){
    push @profileElements,SOAP::Data->name('alleleNumber' => \SOAP::Data->value(
      SOAP::Data->name('locus' => $locus),
      SOAP::Data->name('id' => $allele)));
  }
  my $soapResponse = $self->soap->getRelatedSTsByProfile($$args{database},$$args{match},@profileElements);
  unless ($soapResponse->fault){
    for my $t ($soapResponse->valueof('//ST')) {
      push(@ST,$t);
    }
  } else {
      $self->_error($soapResponse);
  }
  return @ST;
}
sub getRelatedSTsByST{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my(@ST); # STs to return
  my $soapResponse = $self->soap->getRelatedSTsByST($$args{database},$$args{match},$$args{ST});
  unless ($soapResponse->fault){
    for my $t ($soapResponse->valueof('//ST')) {
      push(@ST,$t);
    }
  } else {
    $self->_error($soapResponse);
  } 
  return @ST;
}
# Returns list of STs that contain all the alleles specified. Any number of loci can be included in a query - include all loci for an exact match.
# arguments: database, profile
# profile example: %profile=('abcZ'=>4,'adk_4') which will return any ST with those alleles
sub getSTsByAlleles{
  use SOAP::Lite;
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my(@ST); # STs to return
  my @profileElements;
  while( my($locus,$allele)=each %{$$args{profile}}){
    push @profileElements,SOAP::Data->name('alleleNumber' => \SOAP::Data->value(
      SOAP::Data->name('locus' => $locus),
      SOAP::Data->name('id' => $allele)));
  }
  my $soapResponse = $self->soap->getSTs($$args{database},@profileElements);
  unless ($soapResponse->fault){
    for my $t ($soapResponse->valueof('//ST')) {
      push(@ST,$t);
    }
  } else {
    $self->_error($soapResponse);
  }
  return @ST;
}
# Returns a list of isolate ids that match all fields. 
# Searching is not case-sensitive. 
# Any number of fields can be passed as arguments. 
# By passing an exactMatch boolean parameter, you can set whether exact or partial matches are used.
sub isolateQuery{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my (@elements,@isolateId);
  while( my($field,$value)=each(%{$$args{fields}}) ){
  #foreach my $field (keys %{$$args{fields}}){
    push @elements,SOAP::Data->name('field' => \SOAP::Data->value(
      SOAP::Data->name('name' => $field),
      SOAP::Data->name('value' => $value)));
  }
  my $soapResponse = $self->soap->isolateQuery($$args{database},@elements,$$args{exactMatch});
  unless ($soapResponse->fault){
    for my $t ($soapResponse->valueof('//id')) {
      push(@isolateId,$t);
    }
  } else {
    $self->_error($soapResponse);
  }
  return @isolateId;
}
sub locusBlast{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  my(@result);
  my $soapResponse = $self->soap->locusBlast($$args{database},$$args{locus},$$args{seq}->seq,$$args{numresults});
  unless ($soapResponse->fault){
    for my $t ($soapResponse->valueof('//blastMatch')) {
      push(@result,$t);
    }
  } else {
      $self->_error($soapResponse);
  }
  $$args{hit}=\@result;
  my $in=$self->_pubmlstProcessBlastResults($args);
  return $in;
}
# Finds a database match to an allele sequence. 
# returns a hash {result=>$result,diffs=>array({position,nucleotide1,nucleotide2}),...)}
# $result is the match
# diffs is an array of hashes of the substitutions
sub locusQuery{
  my($self,$args)=@_;
  $args=$self->_processArgs($args);
  #$$args{sequence}=~$self->_trim($$args{sequence});
  my(@diffs,$result);
  #print Dumper $args;exit;
  my $soapResponse = $self->soap->locusQuery($$args{database},$$args{locus},$$args{seq}->seq);
  unless ($soapResponse->fault){
    $result=$soapResponse->result();
    for my $t ($soapResponse->valueof('//substitution')) {
      push(@diffs,$t);
    }
  } else {
      $self->_error($soapResponse);
  }
  return (result=>$result,diffs=>\@diffs);
}


###### aliases ######
# an alias to the SOAP object
# so that you can do $self->soap->... instead of $self->{'SOAP'}->...
sub soap{
  my($self)=@_;
  return $self->{SOAP};
}
# an alias to the alias of the SOAP object
sub SOAP{
  my($self)=@_;
  return $self->soap;
}

####### subroutines that are specific to each repository ####
sub _pubmlstGetAllele{
  use Bio::Seq;
  my($self,$args)=@_;
  my($seq,$soapResponse);
  $soapResponse = $self->{'SOAP'}->getAlleleSequence($$args{database},$$args{locus},$$args{allele});
  unless ($soapResponse->fault){
      $seq=Bio::Seq->new(-seq=>$soapResponse->result(),-id=>$self->_formatFastaId($$args{locus},$$args{allele}),-format=>'fasta');
  } else {
      $self->_error($soapResponse);
  }
  return $seq;
}
sub _pubmlstBlast{
  my($self,$args)=@_;
  my(@hit,$soapResponse);
  my $query=$$args{sequence}||$$args{seq}->seq;
  $soapResponse = $self->{SOAP}->blast($$args{database},$query,$$args{numresults});
  unless ($soapResponse->fault){
    @hit=$soapResponse->valueof('//blastMatch');
  } else {
    $self->_error($soapResponse);
  }
  return @hit;
}
sub _pubmlstProcessBlastResults{
  my($self,$args)=@_;
  my @hit=@{$args->{hit}} # dereference
     or $self->_error("\@hit was not given in the argument hash.");
  # Format a string from the hits, formatted like -m 8
  # queryid subjectid %identity alnLength mismatches gaps
  # qStart qEnd sStart sEnd eValue score
  my $numHits=@hit;
  my $blast8; # -m 8 blast output
  my $queryid=$$args{seq}->id||"query"; # it's either the seq id or a default  
  for(my $i=0;$i<$numHits;$i++){
  	my $percentIdentity=1-$hit[$i]->{mismatches}/$hit[$i]->{length}; # 1-mismatches/length
    my $str=join("\t",(
           $queryid,
           $hit[$i]->{locus}."-".$hit[$i]->{id}, # subjectid
           sprintf("%.2f",$percentIdentity*100), # just two decimals for percent identity
           $hit[$i]->{length},
           $hit[$i]->{mismatches},
           $hit[$i]->{gaps},
           1, # qstart
           $hit[$i]->{length}, # stop coordinate is just the length
           1, # sstart
           $hit[$i]->{length},
           1, # score
           1 # e-value
         ));
    $blast8.=$str."\n";
  }
  # make a searchIO from the string
  my $blast8fh = new IO::String($blast8);
  my $in = new Bio::SearchIO(-format => 'blasttable',
                             -fh=>$blast8fh);
  return $in;
}
sub _pubmlstGetAlleleCount{
  my($self,$args)=@_;
  my($count);
  my $soapResponse = $self->{'SOAP'}->getAlleleCount($$args{database},$$args{locus});
  unless ($soapResponse->fault){
    $count=$soapResponse->result();
  } else {
    $self->_error($soapResponse);
  }
  return $count;
}
sub _pubmlstGetAlleles{
  use Bio::Seq;
  my($self,$args)=@_;
  my(@seq,$soapResponse);
  $soapResponse = $self->{'SOAP'}->getAlleleSequences($$args{database},$$args{locus})
    or $soapResponse = $self->{'SOAP'}->getAlleleSequences($$args{database},$$args{locus}); # one more try
  unless ($soapResponse->fault){
    for my $t ($soapResponse->valueof('//allele')) {
      my %tmpArgs=%$args;
      $tmpArgs{sequence}=$t->{'sequence'};
      $tmpArgs{allele}=$t->{'id'};
      my $seq=$self->_makeSimpleSeq(\%tmpArgs);
      push(@seq,$seq);
    }
  } else {
    $self->_error($soapResponse);
  }
  return @seq;
}
sub _pubmlstGetAlleleSequencesURL{
  my($self,$args)=@_;
  my($url,$soapResponse);
  $soapResponse = $self->soap->getAlleleSequencesURL($$args{database},$$args{locus});
  unless ($soapResponse->fault){
    $url=$soapResponse->result();
  } else {
    $self->_error($soapResponse);
  }
  return $url;
}
sub _pubmlstGetClonalComplex{
  my($self,$args)=@_;
  my($CC);
  my $soapResponse = $self->soap->getClonalComplex($$args{database},$$args{ST});
  unless ($soapResponse->fault){
    $CC=$soapResponse->result();
  } else {
    $self->_error($soapResponse);
    $CC="";
  }
  return $CC;
}
sub _pubmlstGetClonalComplexes{
  my($self,$args)=@_;
  my(@CC);
  my $soapResponse = $self->soap->getClonalComplexes($$args{database});
  unless ($soapResponse->fault){
    @CC=$soapResponse->valueof('//complex');
  } else {
    $self->_error($soapResponse);
  }
  $self->_sortCcs(\@CC);
  return @CC;
}
sub _pubmlstGetIsolateCount{
  my($self,$args)=@_;
  my($count);
  my $soapResponse = $self->soap->getIsolateCount($$args{database});
  unless ($soapResponse->fault){
      $count=$soapResponse->result();
  } else {
      $self->_warn($soapResponse);
  }
  return $count;
}
sub _pubmlstGetReverseConsensus{
  my($self,$args)=@_;
  my($seq);
  my $soapResponse = $self->soap->getReverseConsensus($$args{database},$$args{locus},$$args{length});
  unless ($soapResponse->fault){
    $$args{allele}="ReverseConsensus";
    $$args{sequence}=$soapResponse->result();
    $seq=$self->_makeSimpleSeq($args);
  } else {
    $self->_error($soapResponse);
  }
  return $seq;
}
sub _pubmlstGetForwardConsensus{
  my($self,$args)=@_;
  my($seq);
  my $soapResponse = $self->soap->getForwardConsensus($$args{database},$$args{locus},$$args{length});
  unless ($soapResponse->fault){
    $$args{allele}="ForwardConsensus";
    $$args{sequence}=$soapResponse->result();
    $seq=$self->_makeSimpleSeq($args);
  } else {
    $self->_error($soapResponse);
  }
  return $seq;
}
##### internal functions ######  
sub _sortByFirstNumber{
  my($self,$CC)=@_;
  @$CC=sort {
    # c and d are proxies so that a and b are not changed
    my $c=$a;
    my $d=$b;
    $c=~s/\D+(\d+).+/$1/;
    $d=~s/\D+(\d+).+/$1/;
    $c<=>$d;
  } @$CC;
}
# sort a list of clonal complexes by reference
sub _sortCcs{
  my($self,$CC)=@_;
  # I want to sort these numerically by the MLST definition 
  # (as opposed to alphabetically or by MLEE)
  $self->_sortByFirstNumber($CC);
}
# make a seq object, given a sequence, locus, and allele
sub _makeSimpleSeq{
  use Bio::Seq;
  my($self,$args)=@_;
  my $seq=Bio::Seq->new(
     -seq=>$$args{sequence},
     -id=>$self->_formatFastaId($$args{locus},$$args{allele})
  );
  return $seq;
}
# format a a locus and allele for a defline
sub _formatFastaId{
  my($self,$locus,$allele)=@_;
  return "$locus-$allele";
}

1;
# I freaking hate having to put a 1 here.  Stupid Perl.
__END__

=head1 NAME

MLST - Perl extension for pubmlst.org's API.
Most subroutines were adopted from Keith Jolley's 
scripts found at pubmlst.org/api

=head1 SYNOPSIS

  # get a sequence from PubMLST
  use MLST;
  $mlst=MLST->new();
  $seq=$mlst->getAlleleSequence('neisseria','abcZ',5);
  print ">".$seq->id()."\n".$seq->seq
  
  # you can pass any default arguments when initializing
  # such as database or locus
  my $pubmlst=MLST->new({database=>$database});
  print Dumper $pubmlst->getAllele({database=>$database,locus=>$locus,allele=>$allele});
  print Dumper $pubmlst->blast({sequence=>$sequence,numresuls=>3});
  print Dumper $pubmlst->getAlleleCount({locus=>$locus});
  print Dumper $pubmlst->getAlleles({locus=>$locus});
  print Dumper $pubmlst->getAlleleSequencesURL({locus=>$locus});
  print Dumper $pubmlst->getClonalComplexes({});
  print Dumper $pubmlst->getDatabaseList();
  print Dumper $pubmlst->getIsolateCount();

=head1 DESCRIPTION

MLST is used for interfacing with pubmlst.org.  In the future, it 
should allow for interfacing with all MLST sites.



=head2 EXPORT

None by default.



=head1 SEE ALSO

http://pubmlst.org/api/

=head1 AUTHOR

Lee, <lt>lskatz@gatech.edu<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2009 by Lee Katz

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.


=cut
